package tplenguajes.grafo;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class DirectedGraph<V> implements Graph<V>
{
	// ------------------------- Inner properties -------------------------
	
	private Set<V> vertices = new HashSet<V>();
	private Set<Edge<V>> edges  = new HashSet<Edge<V>>();
	private Map<V,Edges> vertexMap = new HashMap<V, Edges>();
	
	// ------------------------- Public methods -------------------------
	
	@Override
	public boolean addVertex(V v)
	{
		if ( vertices.add(v) )
		{
			vertexMap.put(v, getNewEdges() );
			return true;
		}
		
		return false;
	}

	@Override
	public boolean addEdge(V src, V dst, Edge<V> e)
	{
		if (e==null) {
			throw new NullPointerException();
        } else if (edges.contains(e)) {
            return false;
        }
		
		assertVertexExists(src);
		assertVertexExists(dst);
		
		edges.add(e);
		
		vertexMap.get(src).addOutgoingEdge(e);
		vertexMap.get(dst).addIncomingEdge(e);
		
		return true;
	}

	@Override
	public boolean removeVertex(V v)
	{
		if (containsVertex(v))
		{
            for (Edge<V> e : vertexMap.get(v).getIncoming())
            	removeEdge(e);
            for (Edge<V> e : vertexMap.get(v).getOutgoing())
            	removeEdge(e);

            vertexMap.remove(v);

            return true;
        }
		
		return false;
	}

	@Override
	public boolean removeEdge(Edge<V> e)
	{
		return ( removeEdge(getEdgeSource(e), getEdgeTarget(e)) != null );
	}

	@Override
	public Edge<V> removeEdge(V src, V dst)
	{
		Edge<V> e = getEdge(src, dst);

        if (e != null)
        {
            edges.remove(e);
            
            vertexMap.get(src).removeOutgoingEdge(e);
            vertexMap.get(dst).removeIncomingEdge(e);
        }

        return e;
	}
	
	@Override
	public V getEdgeSource(Edge<V> e)
	{
		return e.getSource();
	}

	@Override
	public V getEdgeTarget(Edge<V> e)
	{
		return e.getTarget();
	}
	
	@Override
	public boolean containsVertex(V v)
	{
		return vertices.contains(v);
	}

	@Override
	public boolean containsEdge(Edge<V> e)
	{
		return edges.contains(e);
	}
	
	@Override
	public Edge<V> getEdge(V src, V dst)
	{
		if ( containsVertex(src) && containsVertex(dst) )
		{
			for ( Edge<V> e : vertexMap.get(src).getOutgoing() )
				if ( e.getTarget() == dst )
					return e;
		}
		
		return null;
	}
	
	@Override
	public Set<V> getVertices() {
		return vertices;
	}

	@Override
	public Set<Edge<V>> getEdges() {
		return edges;
	}
	
	protected Set<Edge<V>> getOutgoingEdges(V v)
	{
		assertVertexExists(v);
		return vertexMap.get(v).getOutgoing();
	}
	
	// ------------------------- Inner methods -------------------------
	
	protected boolean assertVertexExists(V v)
	{
		if ( containsVertex(v) ) {
			return true;
		} else if (v==null) {
			throw new NullPointerException();
        } else {
            throw new IllegalArgumentException("no such vertex in graph");
        }
	}
	
	// ------------------------- Inner classes -------------------------
		
		protected Edges getNewEdges() { return new Edges(); }
		protected Edges getEdges(V v) { return vertexMap.get(v); }
		
		protected class Edges
		{
			private Set<Edge<V>> incoming = new HashSet<Edge<V>>();
			private Set<Edge<V>> outgoing = new HashSet<Edge<V>>();
	        
	        public void addIncomingEdge(Edge<V> e) { incoming.add(e); }
	        public void addOutgoingEdge(Edge<V> e) { outgoing.add(e); }
	        
	        public void removeIncomingEdge(Edge<V> e) { incoming.remove(e); }
	        public void removeOutgoingEdge(Edge<V> e) { outgoing.remove(e); }
	        
	        public Set<Edge<V>> getIncoming() { return incoming; }
			public Set<Edge<V>> getOutgoing() { return outgoing; }
		}
}
